package org.jeecg.modules.service.impl;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.ActivitiException;
import org.activiti.engine.HistoryService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.*;
import org.activiti.image.ProcessDiagramGenerator;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.modules.dto.InAddModelDto;
import org.jeecg.modules.dto.InCopyModelDto;
import org.jeecg.modules.mapper.ActModelMapper;
import org.jeecg.modules.service.IActModelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Optional;

/**
 * @Description: 流程模板
 * @Author: jeecg-boot
 * @Date: 2023-12-01
 * @Version: V1.0
 */
@Service
@Slf4j
public class ActModelServiceImpl extends ServiceImpl<ActModelMapper, InAddModelDto> implements IActModelService {
    @Autowired
    public RepositoryService repositoryService;

    @Autowired
    private HistoryService historyService;

    /**
     * 流程模型列表
     */
    @Override
    public IPage<Model> page(Model model, Integer pageNo, Integer pageSize) {

        ModelQuery modelQuery = repositoryService.createModelQuery().latestVersion().orderByLastUpdateTime().desc();

        final String category = model.getCategory();
        if (StringUtils.isNotBlank(category)) {
            modelQuery.modelCategory(category);
        }

        final String name = model.getName();
        if (StringUtils.isNotBlank(name)) {
            modelQuery.modelName(name);
        }

        final String key = model.getKey();
        if (StringUtils.isNotBlank(key)) {
            modelQuery.modelKey(key);
        }

        Page<Model> page = new Page<Model>(pageNo, pageSize);
        page.setTotal(modelQuery.count());
        page.setRecords(modelQuery.listPage((int) page.getCurrent() - 1, (int) page.getSize()));

        return page;
    }

    @Override
    public IPage<Model> historyPage(Model model, Integer pageNo, Integer pageSize) {
        final ModelQuery modelQuery = repositoryService.createModelQuery().orderByModelVersion().desc();
        Optional.ofNullable(model.getKey()).ifPresent(modelQuery::modelKey);

        Page<Model> page = new Page<Model>(pageNo, pageSize);
        page.setTotal(modelQuery.count());
        page.setRecords(modelQuery.listPage((int) page.getCurrent() - 1, (int) page.getSize()));

        return page;
    }

    @Override
    @Transactional
    public String create(InAddModelDto inAddModelDto) throws TranscoderException, IOException {
        final String name = StringUtils.defaultString(inAddModelDto.getName());
        final String description = StringUtils.defaultString(inAddModelDto.getDescription());
        String message = "";

        ObjectMapper objectMapper = new ObjectMapper();
        ObjectNode editorNode = objectMapper.createObjectNode();
        editorNode.put("id", "canvas");
        editorNode.put("resourceId", "canvas");

        ObjectNode stencilSetNode = objectMapper.createObjectNode();
        stencilSetNode.put("namespace", "http://b3mn.org/stencilset/bpmn2.0#");
        editorNode.set("stencilset", stencilSetNode);

        Model modelData = repositoryService.newModel();

        modelData.setKey(StringUtils.defaultString(inAddModelDto.getKey()));
        modelData.setName(name);
        modelData.setCategory(inAddModelDto.getCategory());

        final long count = repositoryService.createModelQuery().modelKey(modelData.getKey()).count();
        modelData.setVersion(Integer.parseInt(String.valueOf(count + 1)));

        ObjectNode modelObjectNode = objectMapper.createObjectNode();
        modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, name);
        modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, modelData.getVersion());
        modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, description);
        modelData.setMetaInfo(modelObjectNode.toString());

        //保存模型
        repositoryService.saveModel(modelData);
        repositoryService.addModelEditorSource(modelData.getId(), editorNode.toString().getBytes(StandardCharsets.UTF_8));

        // 设置modelId
        log.info("新建流程模板：{}", modelData.getId());
        message = "流程模板创建成功，modelId=" + modelData.getId();

        return message;
    }

    /**
     * 流程复制业务实现
     *
     * @param inCopyModelDto
     */
    @Override
    public void modelCopy(InCopyModelDto inCopyModelDto) throws Exception {
        final String modelId = inCopyModelDto.getModelId();
        if (StringUtils.isBlank(modelId)) {
            // 提示请求参数信息不能为空
            throw new Exception("请求参数modelId不能为空");
        }
        // 获取复制源流程的模型信息
        byte[] bytes = repositoryService.getModelEditorSource(modelId);
        if (bytes == null || bytes.length == 0) {
            // 源流程信息为空,无效复制
            throw new Exception("源流程信息为空，复制取消");
        }
        ObjectMapper objectMapper = new ObjectMapper();
        try {
            // 获取流程设计节点信息
            ObjectNode sourceObjectNode = (ObjectNode) new ObjectMapper().readTree(bytes);
            // 创建复制的目标流程模型, 填充部分默认信息
            Model newModel = repositoryService.newModel();
            ObjectNode modelNode = objectMapper.createObjectNode();
            newModel.setKey(inCopyModelDto.getKey());
            // 填充模型名称, 其中在activiti7.1.M6版本中该name值必须定义为SpringAutoDeployment
            newModel.setName(inCopyModelDto.getName());
            newModel.setVersion(1);
            newModel.setCategory(inCopyModelDto.getCategory());
            // 其余的信息填充可选填
            modelNode.put(ModelDataJsonConstants.MODEL_NAME, inCopyModelDto.getName());
            modelNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
            modelNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, inCopyModelDto.getDescription());
            newModel.setMetaInfo(modelNode.toString());
            // 保存模型
            repositoryService.saveModel(newModel);
            // 对源流程信息进行深拷贝
            ObjectNode editorNode = sourceObjectNode.deepCopy();
            // 以下信息可以通过debugger查看源流程信息, 定义新的流程信息后进行覆盖
            ObjectNode properties = objectMapper.createObjectNode();
            // 流程key的唯一性, 复制后的流程key需要更新, 可定义为源key_copy
            properties.put("process_id", inCopyModelDto.getKey());
            properties.put("name", inCopyModelDto.getName());
            properties.put("author", "");
            // 根据源流程复制后的属性信息覆盖
            editorNode.set("properties", properties);
            // 保存新的流程模型的设计信息
            repositoryService.addModelEditorSource(newModel.getId(), editorNode.toString().getBytes(StandardCharsets.UTF_8));
            // 复制成功, 根据新的流程模型设计流程信息发布, 启动实例验证即可
        } catch (IOException e) {
            log.error(e.getMessage(), e);
        }
    }

    @Override
    public String deploy(String modelId, String processName) {
        Deployment deployment = null;
        String message = "";
        Model modelData = repositoryService.getModel(modelId);
        try {

            byte[] sourceBytes = repositoryService.getModelEditorSource(modelId);
            JsonNode editorNode = new ObjectMapper().readTree(sourceBytes);
            BpmnJsonConverter bpmnJsonConverter = new BpmnJsonConverter();
            BpmnModel bpmnModel = bpmnJsonConverter.convertToBpmnModel(editorNode);

            ProcessDiagramGenerator processDiagramGenerator = new DefaultProcessDiagramGenerator();
            InputStream inputStream = processDiagramGenerator.generateDiagram(bpmnModel, "宋体", "宋体", "宋体");

            // 2. 查询流程定义模型的图片
            byte[] pngBytes = repositoryService.getModelEditorSourceExtra(modelId);

            deployment = repositoryService.createDeployment()
                    .name(processName)
                    .key(modelData.getKey())
                    .enableDuplicateFiltering()
                    .addBpmnModel(modelData.getKey().concat(".bpmn20.xml"), bpmnModel)
                    //.addBytes(modelData.getKey() + ".png", pngBytes) // png资源
//                    .addInputStream(modelData.getKey().concat(".bpmn20.png"), inputStream)
                    .deploy();
        } catch (Exception e) {
            message = "部署失败" + e.getMessage();
            log.error("根据modelId部署流程,异常:{}", e);
        }

        if (deployment != null) {
            modelData.setDeploymentId(deployment.getId());
            repositoryService.saveModel(modelData);

            // 设置流程分类
            List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).list();
            for (ProcessDefinition processDefinition : list) {
                repositoryService.setProcessDefinitionCategory(processDefinition.getId(), modelData.getCategory());
                message = "部署成功，流程ID=" + processDefinition.getId();
            }
            if (list.isEmpty()) {
                message = "部署失败，没有流程。";
            }
        }
        return message;
    }

    /**
     * 导出model的xml文件
     *
     * @throws IOException
     * @throws JsonProcessingException
     */
    @Override
    public void exportXml(String id, HttpServletResponse response) {
        try {
            org.activiti.engine.repository.Model modelData = repositoryService.getModel(id);
            BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
            JsonNode editorNode = new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
            BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
            BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
            byte[] bpmnBytes = xmlConverter.convertToXML(bpmnModel, "utf-8");

            ByteArrayInputStream in = new ByteArrayInputStream(bpmnBytes);
            IOUtils.copy(in, response.getOutputStream());
            String filename = bpmnModel.getMainProcess().getId() + ".bpmn20.xml";
            response.setHeader("Content-Disposition", "attachment; filename=" + filename);
            response.flushBuffer();
        } catch (Exception e) {
            throw new ActivitiException("导出model的xml文件失败，模型ID=" + id, e);
        }
    }

    @Override
    public String getModelXml(String id) {
        try {
            org.activiti.engine.repository.Model modelData = repositoryService.getModel(id);
            BpmnJsonConverter jsonConverter = new BpmnJsonConverter();
            JsonNode editorNode = new ObjectMapper().readTree(repositoryService.getModelEditorSource(modelData.getId()));
            BpmnModel bpmnModel = jsonConverter.convertToBpmnModel(editorNode);
            BpmnXMLConverter xmlConverter = new BpmnXMLConverter();
            byte[] bpmnBytes = xmlConverter.convertToXML(bpmnModel, "utf-8");

            return new String(bpmnBytes);
        } catch (Exception e) {
            throw new ActivitiException("导出model的xml文件失败，模型ID=" + id, e);
        }
    }

    /**
     * 更新Model分类
     */
    @Override
    @Transactional(readOnly = false)
    public void updateCategory(String id, String category) {
        org.activiti.engine.repository.Model modelData = repositoryService.getModel(id);
        modelData.setCategory(category);
        repositoryService.saveModel(modelData);
    }

    /**
     * 删除模型
     *
     * @param id
     * @return
     */
    @Override
    @Transactional(readOnly = false)
    public void delete(String id) {
        repositoryService.deleteModel(id);
    }
}
